package com.sunlands.zlcx.usercenter.config;

import com.sunlands.zlcx.usercenter.common.thread.ThreadPoolTaskExecutorDecorator;
import com.sunlands.zlcx.usercenter.common.thread.ThreadPoolTaskSchedulerDecorator;
import com.sunlands.zlcx.usercenter.exception.SendExceptionUtil;
import com.sunlands.zlcx.usercenter.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ErrorHandler;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Autowired
    private SendExceptionUtil sendExceptionUtil;

    @Bean("gyfTaskExecutor")
    @Override
    public Executor getAsyncExecutor() {
        return getThreadPoolTaskExecutor(6, 12, "gyf-async-", true);
    }

    @Bean("commonTaskExecutor")
    public Executor commonTaskExecutor() {
        return getThreadPoolTaskExecutor(2, 4, "common--async-", true);
    }

    @Bean("vipTaskExecutor")
    public Executor vipTaskExecutor() {
        return getThreadPoolTaskExecutor(2, 4, "vip--async-", true);
    }

    @Bean("vipChargingExecutor")
    public ThreadPoolTaskScheduler vipChargingTaskExecutor() {
        return getThreadPoolTaskScheduler(1, "vip-charging-", true);
    }

    @Bean("scheduledExecutorService")
    public ThreadPoolTaskScheduler scheduledExecutorService() {
        return getThreadPoolTaskScheduler(1, "scheduledExecutorService-", true);
    }

    @Bean("sendError")
    public AsyncTaskExecutor mvcAsyncExecutor() {
        return getThreadPoolTaskScheduler(1, "sendError", true);
    }

    private ThreadPoolTaskScheduler getThreadPoolTaskScheduler(int corePoolSize, String threadNamePrefix, boolean waitForTasksToCompleteOnShutdown) {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskSchedulerDecorator();
        executor.setPoolSize(corePoolSize);
        executor.setThreadNamePrefix(threadNamePrefix);
        //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean，这样这些异步任务的销毁就会先于外部资源的销毁
        executor.setWaitForTasksToCompleteOnShutdown(waitForTasksToCompleteOnShutdown);
        //用来设置线程池中任务的等待时间，如果超过这个时候还没有销毁就强制销毁，以确保应用最后能够被关闭，而不是阻塞住
        executor.setRejectedExecutionHandler(new AbortPolicy());
        executor.setThreadGroup(new CommonThreadGroup("CommonThreadGroup"));
        return executor;
    }

    private ThreadPoolTaskExecutor getThreadPoolTaskExecutor(int corePoolSize, int maxPoolSize, String threadNamePrefix, boolean waitForTasksToCompleteOnShutdown) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorDecorator();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setQueueCapacity(1024);
        executor.setThreadGroup(new CommonThreadGroup("CommonThreadGroup"));
        //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean，这样这些异步任务的销毁就会先于外部资源的销毁
        executor.setWaitForTasksToCompleteOnShutdown(waitForTasksToCompleteOnShutdown);
        //用来设置线程池中任务的等待时间，如果超过这个时候还没有销毁就强制销毁，以确保应用最后能够被关闭，而不是阻塞住
        executor.setRejectedExecutionHandler(new AbortPolicy());
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SpringAsyncExceptionHandler();
    }

    class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            log.error("Exception = handleUncaughtException method = {} params = {}", method, objects);
            log.error("异步线程报错 {} ", throwable.getMessage());
            log.error("", throwable.fillInStackTrace());
            log.error("", throwable);
            sendExceptionUtil.sendException("异步线程报错 : method:" + method + "objects :" + objects + "Exception：" + throwable.getMessage(), new IllegalThreadStateException());
        }
    }


    static class CommonThreadGroup extends ThreadGroup {
        public CommonThreadGroup(String name) {
            super(name);
        }

        public CommonThreadGroup(ThreadGroup parent, String name) {
            super(parent, name);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            log.error("异步线程报错 Thread = {}", t);
            log.error("", e);
        }
    }


    /**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() {
        }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            SendExceptionUtil sendExceptionUtil = SpringContextHolder.getBean(SendExceptionUtil.class);
            sendExceptionUtil.sendException("定时任务线程池数量超过先定数量" + "Task " + r.toString() +
                    " rejected from " +
                    e.toString(), new RejectedExecutionException());
            log.error("\"Task \" + r.toString() +\n" +
                    "                    \" rejected from \" +\n" +
                    "                    e.toString()");
        }
    }
}
